using System;
using System.Collections;
using System.Reflection;
using System.IO;
using System.Security;
using System.Text;
using System.Text.RegularExpressions;
using PaulRz.Util;
using NUnit.Framework;

namespace PaulRz.Util
{
	/// <summary>
	/// Summary description for DllCache.
	/// </summary>
	public sealed class DllCache
	{
		private static DllCache theInstance = null;
		private Hashtable allDlls;
		private static object lockObj = new object();

		private DllCache()
		{
			allDlls = new Hashtable();
		}

		public static DllCache instance()
		{
			if (theInstance == null) 
			{
				theInstance = new DllCache();
			}
			return theInstance;
		}

		public Assembly GetDll(string dllFileName) 
		{
			dllFileName=FUtil.getConfigPath(dllFileName);
			if(allDlls.Contains(dllFileName)) 
			{
				
				return (Assembly)allDlls[dllFileName];
			}

			lock(lockObj)
			{
				if(allDlls.Contains(dllFileName)) 
				{
					return (Assembly)allDlls[dllFileName];
				}

				Assembly result = readDll(dllFileName);
				if(result != null)
				{
					allDlls.Add(dllFileName, result);
				}
				return result;
			}
		}

		private static Assembly readDll(string dllFileName)
		{
			Assembly a = null;

			try
			{
				a = Assembly.LoadFrom(dllFileName);
			}
			catch(ArgumentNullException e)
			{
				throw new ApplicationException(
					String.Format("Failed to load assembly! Given path is a null reference."),
					e);
			}
			catch(FileNotFoundException e)
			{
				throw new ApplicationException(
					String.Format("Failed to load assembly {0}! File was not found.", dllFileName),
					e);
			}
			catch(BadImageFormatException e)
			{
				throw new ApplicationException(
					String.Format("Failed to load assembly {0}! File is not a valid assembly.", dllFileName),
					e);
			}
			catch(SecurityException e)
			{
				throw new ApplicationException(
					String.Format("Failed to load assembly {0}! Security exception occured.", dllFileName),
					e);
			}
			catch(PathTooLongException e)
			{
				throw new ApplicationException(
					String.Format("Failed to load assembly {0}! An assembly or module was loaded twice with two different evidences, or the assembly name is longer than MAX_PATH characters.", dllFileName),
					e);
			}
			catch(ArgumentException e)
			{
				throw new ApplicationException(
					String.Format("Failed to load assembly {0}! The assemblyFile parameter is the empty string.", dllFileName),
					e);
			}

			return a;
		}

		[Test]
		public void TestReadDll()
		{
			try
			{
				string file = null;
				readDll(file);
			}
			catch(ApplicationException ex)
			{
				Regex regex = new Regex("Given path is a null reference.");
				Assert.AreEqual(true, regex.IsMatch(ex.Message));
			}
			try
			{
				readDll("file");
			}
			catch(ApplicationException ex)
			{
				Regex regex = new Regex("File was not found.");
				Assert.AreEqual(true, regex.IsMatch(ex.Message));
			}
			try
			{
				readDll("");
			}
			catch(ApplicationException ex)
			{
				Regex regex = new Regex("The assemblyFile parameter is the empty string.");
				Assert.AreEqual(true, regex.IsMatch(ex.Message));
			}
		}
				

		public object GetObject(string dllFileName, string typeName)
		{
			Assembly a = GetDll(dllFileName);

			if(a == null)
			{
				return null;
			}

			object obj = null;

			Type[] types = a.GetTypes();

			foreach(Type t in types)
			{
			    string[] typeSpecs = t.Name.Split('.');
				if(String.Compare(typeSpecs[typeSpecs.Length-1], typeName, StringComparison.InvariantCultureIgnoreCase)==0)
				{
					try
					{
						obj = Activator.CreateInstance(t);
					}
					catch(Exception e)
					{
						throw new ApplicationException(
							String.Format("Failed to create instance of type {0} form assembly file {1}", typeName, dllFileName),
							e
							);
					}
				}
			}

			return obj;
		}
	}
}
